package com.gxa.learning.pc.service.impl;

import com.gxa.learning.core.common.ExceptionCode;
import com.gxa.learning.core.exception.BusinessException;
import com.gxa.learning.core.pojo.*;
import com.gxa.learning.core.util.JsonUtil;
import com.gxa.learning.pc.mapper.MainMapper;
import com.gxa.learning.pc.service.CourseService;
import com.gxa.learning.pc.service.MainService;
import com.gxa.learning.pc.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.*;
import org.springframework.stereotype.Service;

import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * @author YXQ
 * @date 2021/12/8 20:44
 */
@Service("mainService")
public class MainServiceImpl implements MainService {
    @Autowired
    CourseService courseService;

    @Autowired
    UserService userService;

    @Autowired
    MainMapper mainMapper;

    @Autowired
    RedisTemplate<String, Object> redisTemplate;

    /**
     * 热门课程的键
     */
    public static final String HOT_COURSE = "hotCourse";

    /**
     * 课程的键
     */
    public static final String COURSE = "course";

    /**
     * 培训的键
     */
    public static final String PROJECT = "project";

    /**
     * 收藏
     */
    public static final String LOVE = "love";

    @Override
    public void incrCourseScore(Map<String, Object> course) {
        // zSet数据集 key 热门课程 score 点击量 member 课程的id号:{img:xxx.jpg,}
        ZSetOperations<String, Object> ops = redisTemplate.opsForZSet();
        // 提取课程的信息，封装到一个简介的课程对象上
        CourseForMain courseForMain = new CourseForMain();
        // 课程编号
        Object id = course.get("id");
        // 如果编号不为空
        if (id != null) {
            // 存入到对象中
            courseForMain.setId(Long.parseLong(id.toString()));
        }
        // 上下架状态
        Object status = course.get("status");
        // 如果 为上架才加到redis中 不为空表示上架
        if (status != null) {
            // 课程标题
            Object title = course.get("title");
            // 如果标题不为空
            if (title != null) {
                // 存入到对象中
                courseForMain.setTitle(title.toString());
            }
            // 课程封面
            Object cover = course.get("cover");
            // 如果 课程封面 不为空
            if (cover != null) {
                // 存入到对象中
                courseForMain.setCover(cover.toString());
            }
            // 课程类型
            Object type = course.get("type");
            // 如果 课程类型 不为空
            if (type != null) {
                // 存入到对象中
                courseForMain.setType(type.toString());
            }
            // 难度等级
            Object level = course.get("level");
            // 如果标题不为空
            if (level != null) {
                // 存入到对象中
                courseForMain.setLevel(level.toString());
            }
            // 课程描述
            Object explain = course.get("explain");
            // 如果 课程描述 不为空
            if (explain != null) {
                // 存入到对象中
                courseForMain.setExplain(explain.toString());
            }
            // 将对象转化为字符串作为键
            String member = JsonUtil.objectToString(courseForMain);
            // 获取redis中这门课程的点击量
            Long rank = ops.rank(HOT_COURSE, member);
            // 如果点击量不存在，则说明是第一次点击,设置默认的分数为1
            if (rank == null) {
                // 不存在则设置一个,分数为1,这是第一次点击
                ops.add(HOT_COURSE, member, 1);
            } else {
                // 如果存在,获取当前的点击量
                Double score = ops.score(HOT_COURSE, member);
                // 将点击量+1存入redis
                ops.incrementScore(HOT_COURSE, member, 1);
            }
        }
    }

    @Override
    public List<CourseForMain> getHotCourseByScore(Long start, Long end) {
        // zSet数据集 key 热门课程 score 点击量 member 课程的id号:{img:xxx.jpg,}
        ZSetOperations<String, Object> ops = redisTemplate.opsForZSet();
        // 根据分数的排名拿到member的值的集合
        Set<Object> setS = ops.reverseRange(HOT_COURSE, start, end);
        // 如果是第一次查询热门课程需要从数据库添加
        if (setS == null || setS.isEmpty()) {
            // 向redis缓存中添加数据
            // 从数据库查询16条数据加入到redis中
            // 单独写一个返回map的课程查询方法
            List<Map<String, Object>> list = mainMapper.hotCourse();
            // 将从数据库查到的课程添加到redis中
            for (Map<String, Object> map : list) {
                // 直接添加，存在会忽略，不存在才添加
                incrCourseScore(map);
            }
        }
        // 添加之后再查一次
        // 根据分数的排名拿到member的值的集合
        Set<Object> objects = ops.reverseRange(HOT_COURSE, start, end);
        // 生成一个list用于存放转化后的数据
        List<CourseForMain> list = new ArrayList<>(16);
        // 判断拿到的数据是否为空，不为空加到集合中
        if (objects != null) {
            // 遍历集合并转化为 CourseForMain 对象
            for (Object object : objects) {
                // 通过工具类将对象转化成数组
                CourseForMain courseForMain = JsonUtil.stringToObject(object.toString(), CourseForMain.class);
                // 加到集合中
                list.add(courseForMain);
            }
        }
        return list;
    }

    @Override
    public void addCourse(Course course) {
        // 获取课程的编号
        Long id = course.getId();
        // string类型的操作集
        ValueOperations<String, Object> stringOps = redisTemplate.opsForValue();
        // 查看redis中是否存在该商品的详细信息
        Object o = stringOps.get(COURSE + id);
        // 如果不存在添加一条
        if (o == null) {
            // 把对象序列化成string
            String courseString = JsonUtil.objectToString(course);
            // 把课程加入到redis中
            stringOps.set(COURSE + id, courseString);
        }
    }

    @Override
    public void addProject(Project project) {
        // 获取培训信息的编号
        Long id = project.getProjectId();
        // string类型的操作集
        ValueOperations<String, Object> stringOps = redisTemplate.opsForValue();
        // 查看redis中是否存在该培训的详细信息
        Object o = stringOps.get(PROJECT + id);
        // 如果不存在添加一条
        if (o == null) {
            // 把对象序列化成string
            String courseString = JsonUtil.objectToString(project);
            // 把课程加入到redis中
            stringOps.set(PROJECT + id, courseString);
        }
    }

    @Override
    public void loveCourse(Long userId, Long courseId, Integer love) throws BusinessException {
        // string类型的操作集，在收藏时，如果没有数据添加一条
        ValueOperations<String, Object> stringOps = redisTemplate.opsForValue();
        // list类型的操作集,key:value userId:id
        ListOperations<String, Object> listOps = redisTemplate.opsForList();
        // 查看redis中是否存在该课程的详细信息，没有从数据库添加到缓存
        Object o = stringOps.get(COURSE + courseId);
        // 如果不存在添加一条
        if (o == null) {
            // 通过课程的id从数据库里面查询
            Course course = courseService.selectById(courseId);
            // 把对象序列化成string
            String courseString = JsonUtil.objectToString(course);
            // 把课程加入到redis中
            stringOps.set(COURSE + courseId, courseString);
        }
        // 收藏
        if (love.equals(1)) {
            // 先判断用户是否已经收藏过这个课程
            Long num = 0L;
            // 取消收藏
            Long remove = listOps.remove(userId + LOVE, 1, COURSE + courseId);
            // 如果没有取消成功，说明该课程未收藏
            if (num.equals(remove)) {
                // 收藏
                listOps.leftPush(userId + LOVE, COURSE + courseId);
            } else {
                // 收藏回来
                listOps.leftPush(userId + LOVE, COURSE + courseId);
                // 您已经收藏过了，请不要重复提交
                throw new BusinessException(ExceptionCode.DONT_SUBMIT_AGAIN);
            }
        }
        // 取消收藏
        else if (love.equals(0)) {
            // 取消收藏
            listOps.remove(userId + LOVE, 1, COURSE + courseId);
        }
    }

    @Override
    public List<Course> getLoveCourse(Long userId) {
        // list类型的操作集,key:value userId:id
        ListOperations<String, Object> listOps = redisTemplate.opsForList();
        // string类型的操作集
        ValueOperations<String, Object> stringOps = redisTemplate.opsForValue();
        // 获取键值为userId + LOVE的所有的课程id
        List<Object> range = listOps.range(userId + LOVE, 0, -1);
        // 准备给前端返回的数据
        List<Course> list = new ArrayList<>();
        if (range != null) {
            // 遍历所有的键值
            for (Object o : range) {
                // 将课程的id转换成字符串
                String s = o.toString();
                // 通过键值在string类型中拿到键值
                Object o1 = stringOps.get(s);
                // 将值转化成string类型的对象
                if (o1 != null) {
                    // 将拿到的键值转化成对象
                    Course course = JsonUtil.stringToObject(o1.toString(), Course.class);
                    // 放入集合中
                    list.add(course);
                }
            }
        }
        return list;
    }

    /**
     * 用于评论主体的类(Hash)的辅助Key--->"course:":课程id
     * Key: course:课程id:remark:id  ObjectKey: 字段 Value:字段值
     * Key: course:课程id:remark:id 表示某门课程的某条评论
     */
    private final static String REMARK_COURSE = "course:";

    /**
     * 用于评论主体的类(Hash)的辅助Key--->":remark:"
     * Key: course:课程id:remark:id 表示某门课程的某条评论
     */
    private final static String REMARK = ":remark:";

    /**
     * 用于评论自增的的辅助Key--->:remarkId
     * Key: course:课程id:remarkId  Value: id
     */
    private final static String REMARK_ID = ":remarkId";

    /**
     * 用户点赞的列表(Set)的Key
     * Key:userId : likeRemark Value: courseId:remarkId
     * 键:用户的收藏列表 值: XX课程:XX评论
     */
    private final static String LIKE_REMARK = ":likeRemark";

    /**
     * 用户信息的Key
     */
    public final static String USER = "user:";

    @Override
    public void comment(Long userId, Long courseId, String remark, Long pId, String reply) {
        // String的操作集--->做评论自增的id
        ValueOperations<String, Object> stringOps = redisTemplate.opsForValue();
        // 准备自增的评论id的Key--->course:课程id:remarkId
        String remarkIdKey = REMARK_COURSE + courseId + REMARK_ID;
        // 先获取这个值是否存在
        Object o = stringOps.get(remarkIdKey);
        // 不存在时新建，存在时自增+1
        if (o == null) {
            // 不存在时新建
            stringOps.set(remarkIdKey, 1);
        } else {
            // 存在时自增+1
            stringOps.increment(remarkIdKey);
        }
        // 拿到这个id值作为评论的自增的id值
        String idString = Objects.requireNonNull(stringOps.get(remarkIdKey)).toString();
        // 将String类型的id转化为Long类型
        Long id = Long.parseLong(idString);
        // Hash的操作集--->做每条评论的内容
        HashOperations<String, Object, Object> hashOps = redisTemplate.opsForHash();
        // 评论的主体通过Hash来做
        // Key: course:课程id:remark:id  ObjectKey: 字段 Value:字段值
        // 给XX课程下，添加某条评论的记录
        // id 评论的编号
        hashOps.put(REMARK_COURSE + courseId + REMARK + id, "id", id);
        // userId 用户的编号,发表评论的人
        hashOps.put(REMARK_COURSE + courseId + REMARK + id, "userId", userId);
        // courseId 课程的编号
        hashOps.put(REMARK_COURSE + courseId + REMARK + id, "courseId", courseId);
        // remark 评论的内容
        hashOps.put(REMARK_COURSE + courseId + REMARK + id, "remark", remark);
        // pId 直接评论->pId为0,回复->pid为一级评论的编号
        hashOps.put(REMARK_COURSE + courseId + REMARK + id, "pid", pId);
        // reply 被回复的人
        hashOps.put(REMARK_COURSE + courseId + REMARK + id, "reply", reply);
        // star 评论刚发表时，点赞数量为0
        hashOps.put(REMARK_COURSE + courseId + REMARK + id, "star", 0);
        // 发表评论的时间 为当前时间
        Date createTime = new Date();
        // 设置日期格式化的格式
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        // 将日期格式化
        String create = simpleDateFormat.format(createTime);
        // create 发表评论的时间
        hashOps.put(REMARK_COURSE + courseId + REMARK + id, "create", create);
    }

    @Override
    public void likeComment(Long userId, Long courseId, Long remarkId, Integer isLike) {
        // Set的操作集--->做用户点赞过的评论列表
        SetOperations<String, Object> setOps = redisTemplate.opsForSet();
        // Hash的操作集--->获取每条评论的内容--->做点赞量的增加和减少
        HashOperations<String, Object, Object> hashOps = redisTemplate.opsForHash();
        // isLike---> 1点赞 0取消点赞
        if (isLike == 1) {
            // 表示用户点赞
            // 用户点赞列表,向set中添加：一条评论的id
            setOps.add(userId + LIKE_REMARK + courseId, remarkId);
            // 用户每次点赞会让点赞的评论对应的Hash的数据中的点赞量+1
            hashOps.increment(REMARK_COURSE + courseId + REMARK + remarkId, "star", 1);
        } else if (isLike == 0) {
            // 表示用户取消点赞
            // 用户点赞列表,向set中去除：一条评论的id
            setOps.remove(userId + LIKE_REMARK + courseId, remarkId);
            // 用户每次取消点赞会让点赞的评论对应的Hash的数据中的点赞量-1
            hashOps.increment(REMARK_COURSE + courseId + REMARK + remarkId, "star", -1);
        }
    }

    @Override
    public List<CommentVo> getCommentList(Long userId, Long courseId) {
        // Set的操作集--->做用户点赞过的评论列表
        // 看评论列表中的每条评论的id是否存在于这个点赞列表中
        SetOperations<String, Object> setOps = redisTemplate.opsForSet();
        // Hash的操作集--->获取每条评论的内容
        HashOperations<String, Object, Object> hashOps = redisTemplate.opsForHash();
        // String的操作集--->查询用户信息
        ValueOperations<String, Object> stringOps = redisTemplate.opsForValue();
        // 获取该课程下的所有评论装入List中
        List<CommentVo> list = new ArrayList<>(10);
        // 循环变量
        int i = 1;
        // 死循环获取某门课程下的所有的评论
        while (true) {
            // 拿到Key下的所有字段
            Set<Object> keys = hashOps.keys(REMARK_COURSE + courseId + REMARK + i);
            // 拿到Key为空是结束循环
            if (keys.isEmpty()) {
                // 结束循环
                break;
            }
            // 拿到对应Hash将这个Hash对象转化为Map
            // 获取这些字段值 id,userId,courseId,remark,pid,reply,star,create
            Map<Object, Object> entries = hashOps.entries(REMARK_COURSE + courseId + REMARK + i);
            // 放到评论对象身上
            Comment comment = new Comment();
            // 放到评论的Vo对象上
            CommentVo commentVo = new CommentVo();
            // 取出对应值存入对象中
            // 获取id属性
            Long id = Long.parseLong(entries.get("id").toString());
            // 同时向comment和commentVo中添加
            // comment给redis服务,commentVo服务
            // 添加到属性字段中
            // 添加评论的id
            comment.setId(id);
            commentVo.setId(id);
            // 获取用户的编号
            Long userId1 = Long.parseLong(entries.get("userId").toString());
            // 添加用户的编号
            comment.setUserid(userId1);
            commentVo.setUserid(userId1);
            // 根据用户编号查询redis中是否存在用户信息，不存在则添加
            Object o = stringOps.get(USER + userId1);
            // 如果不存在
            if (o == null) {
                // 不存在，到数据库查询用户信息，存入redis中
                User user = userService.selectById(userId1);
                // 如果用户存在将user转化成字符串
                if (user == null || user.getId() == null) {
                    // 该用户已注销
                    commentVo.setUserName("该用户已注销");
                    commentVo.setUserHeadPhoto("XXXX.jpg");
                } else {
                    // 第一次直接加到对象上
                    // 获取用户名字和头像存入redis中
                    commentVo.setUserName(user.getName());
                    commentVo.setUserHeadPhoto(user.getPhoto());
                    // 存在将对象转成字符串存入redis中
                    String s = JsonUtil.objectToString(user);
                    // 存入redis
                    stringOps.set(USER + userId1, s);
                }
            } else {
                // 存在则将数据转化成对象赋值给Vo
                String s = o.toString();
                // 将字符串转化成对象
                User user = JsonUtil.stringToObject(s, User.class);
                // 获取用户名字和头像存入redis中
                commentVo.setUserName(user.getName());
                commentVo.setUserHeadPhoto(user.getPhoto());
            }
            // 设置课程id
            comment.setCourseId(Long.parseLong(entries.get("courseId").toString()));
            commentVo.setCourseId(Long.parseLong(entries.get("courseId").toString()));
            // 设置评论内容
            comment.setRemark(entries.get("remark").toString());
            commentVo.setRemark(entries.get("remark").toString());
            // 设置父id
            comment.setPId(Long.parseLong(entries.get("pid").toString()));
            commentVo.setPId(Long.parseLong(entries.get("pid").toString()));
            // 设置回复人
            comment.setReply(entries.get("reply").toString());
            commentVo.setReply(entries.get("reply").toString());
            // 设置点赞数
            comment.setStar(Long.parseLong(entries.get("star").toString()));
            commentVo.setStar(Long.parseLong(entries.get("star").toString()));
            // 存入日期对象
            String create = entries.get("create").toString();
            // 将字符串转化为日期为 Timestamp 格式
            Timestamp timestamp = Timestamp.valueOf(create);
            // 转化为日期对象
            Date date = new Date(timestamp.getTime());
            // 存入对象中
            comment.setCreate(date);
            commentVo.setCreate(date);
            // 默认没有点赞此评论
            comment.setIsLike(0);
            commentVo.setIsLike(0);
            // 检查这个对象是否被这个用户收藏
            Boolean member = setOps.isMember(userId + LIKE_REMARK + courseId, id);
            // 检查空
            if (member != null) {
                // 如果存在返回数据中点赞为1
                if (member) {
                    // 表示确定点赞
                    comment.setIsLike(1);
                    commentVo.setIsLike(1);
                }
            }
            // 添加到集合中
            list.add(commentVo);
            // 循环变量自增
            i++;
        }
        return list;
    }
}